package l2hc.gameserver;

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.logging.Level;
import java.util.logging.Logger;

import l2hc.commons.dbutils.DbUtils;
import l2hc.gameserver.database.DatabaseFactory;
import l2hc.gameserver.network.GameClient;
import l2hc.gameserver.network.serverpackets.Ex2ndPasswordAck;
import l2hc.gameserver.network.serverpackets.Ex2ndPasswordCheck;
import l2hc.gameserver.network.serverpackets.Ex2ndPasswordVerify;
import l2hc.gameserver.network.serverpackets.L2GameServerPacket;
import l2hc.gameserver.utils.Util;
import l2hc.loginserver.Base64;
import l2hc.loginserver.L2LoginClient;

public class SecondaryPasswordController
{
	private final Logger _log = Logger.getLogger(SecondaryPasswordController.class.getName());
	private final GameClient _activeClient;
	private String _password;
	private int _wrongAttempts;
	private boolean _authed;
	private static final String VAR_PWD = "secauth_pwd";
	private static final String VAR_WTE = "secauth_wte";
	private static final String SELECT_PASSWORD = "SELECT var, value FROM secondary_auth WHERE account_name=? AND var LIKE 'secauth_%'";
	private static final String INSERT_PASSWORD = "INSERT INTO secondary_auth VALUES (?, ?, ?)";
	private static final String UPDATE_PASSWORD = "UPDATE secondary_auth SET value=? WHERE account_name=? AND var=?";
	private static final String INSERT_ATTEMPT = "INSERT INTO secondary_auth VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE value=?";

	public SecondaryPasswordController(GameClient activeClient)
	{
		_activeClient = activeClient;
		_password = null;
		_wrongAttempts = 0;
		_authed = false;
		loadPassword();
	}

	private void loadPassword()
	{
		String value = null;
		Connection con = null;
		PreparedStatement statement = null;
		try
		{
			con = DatabaseFactory.getInstance().getConnection();
			statement = con.prepareStatement(SELECT_PASSWORD);
			statement.setString(1, _activeClient.getLogin());
			ResultSet rs = statement.executeQuery();
			while (rs.next())
			{
				String var = rs.getString("var");
				value = rs.getString("value");
				if (var.equals(VAR_PWD))
				{
					_password = value;
				}
				else if (var.equals(VAR_WTE))
				{
					_wrongAttempts = Integer.parseInt(value);
				}
			}
			statement.close();
		}
		catch (Exception e)
		{
			_log.log(Level.SEVERE, "Error while reading password.", e);
		}
		finally
		{
			DbUtils.closeQuietly(con);
		}
	}

	public boolean savePassword(String password)
	{
		if (passwordExist())
		{
			_log.warning("[SecondaryPasswordController]" + _activeClient.getLogin() + " forced savePassword");
			_activeClient.closeNow(true);
			return false;
		}

		if (!validatePassword(password))
		{
			_activeClient.sendPacket(new L2GameServerPacket[] { new Ex2ndPasswordAck(Ex2ndPasswordAck.WRONG_PATTERN) });
			return false;
		}
		password = cryptPassword(password);
		Connection con = null;
		PreparedStatement statement = null;
		try
		{
			con = DatabaseFactory.getInstance().getConnection();
			statement = con.prepareStatement(INSERT_PASSWORD);
			statement.setString(1, _activeClient.getLogin());
			statement.setString(2, VAR_PWD);
			statement.setString(3, password);
			statement.execute();
			statement.close();
		}
		catch (Exception e)
		{
			_log.log(Level.SEVERE, "Error while writing password.", e);
            return false;
		} 
		finally 
		{ 
			DbUtils.closeQuietly(con);
		}
		_password = password;
		return true;
	}

	public boolean insertWrongAttempt(int attempts)
	{
		Connection con = null;
		PreparedStatement statement = null;
		try
		{
			con = DatabaseFactory.getInstance().getConnection();
			statement = con.prepareStatement(INSERT_ATTEMPT);
			statement.setString(1, _activeClient.getLogin());
			statement.setString(2, VAR_WTE);
			statement.setString(3, Integer.toString(attempts));
			statement.setString(4, Integer.toString(attempts));
			statement.execute();
			statement.close();
		}
		catch (Exception e)
		{
			_log.log(Level.SEVERE, "Error while writing wrong attempts.", e);
	        return false;
		} 
		finally 
		{ 
			DbUtils.closeQuietly(con);
		}
		return true;
	}

	public boolean changePassword(String oldPassword, String newPassword)
	{
		if (!passwordExist())
		{
			_log.warning("[SecondaryPasswordController]" + _activeClient.getLogin() + " forced changePassword");
			_activeClient.closeNow(true);
			return false;
		}

		if (!checkPassword(oldPassword, true)) 
		{
			return false;
		}
		if (!validatePassword(newPassword))
		{
			_activeClient.sendPacket(new L2GameServerPacket[] { new Ex2ndPasswordAck(Ex2ndPasswordAck.WRONG_PATTERN) });
			return false;
		}

		newPassword = cryptPassword(newPassword);
		Connection con = null;
		try
		{
			con = DatabaseFactory.getInstance().getConnection();
			PreparedStatement statement = con.prepareStatement(UPDATE_PASSWORD);
			statement.setString(1, newPassword);
			statement.setString(2, _activeClient.getLogin());
			statement.setString(3, VAR_PWD);
			statement.execute();
			statement.close();
		}
		catch (Exception e)
		{
			_log.log(Level.SEVERE, "Error while reading password.", e);
            return false;
		} 
		finally 
		{ 
			DbUtils.closeQuietly(con);
		}
		_password = newPassword;
		_authed = false;
		return true;
	}

	public boolean checkPassword(String password, boolean skipAuth)
	{
		password = cryptPassword(password);

		if (!password.equals(_password))
		{
			_wrongAttempts += 1;
			if (_wrongAttempts < Config.SECOND_AUTH_MAX_ATTEMPTS)
			{
				_activeClient.sendPacket(new L2GameServerPacket[] { new Ex2ndPasswordVerify(1, _wrongAttempts) });
				insertWrongAttempt(_wrongAttempts);
				return false;
			}
			L2LoginClient.sendTempBan(_activeClient.getLogin(), _activeClient.getIpAddr(), Config.SECOND_AUTH_BAN_TIME);
			L2LoginClient.sendMail(_activeClient.getLogin(), "SATempBan", new String[] { _activeClient.getIpAddr(), Integer.toString(Config.SECOND_AUTH_MAX_ATTEMPTS), Long.toString(Config.SECOND_AUTH_BAN_TIME), Config.SECOND_AUTH_REC_LINK });
			_log.warning(_activeClient.getLogin() + " - (" + _activeClient.getIpAddr() + ") has inputted the wrong password " + _wrongAttempts + " times in row.");
			insertWrongAttempt(0);
			_activeClient.close( new Ex2ndPasswordVerify(2, Config.SECOND_AUTH_MAX_ATTEMPTS));
			return false;
		}

		if (!skipAuth)
		{
			_authed = true;
			_activeClient.sendPacket(new L2GameServerPacket[] { new Ex2ndPasswordVerify(0, _wrongAttempts) });
		}
		insertWrongAttempt(0);
		return true;
	}

	public boolean passwordExist()
	{
		return _password == null ? false : true;
	}

	public void openDialog()
	{
		if (passwordExist())
		{
			_activeClient.sendPacket(new L2GameServerPacket[] { new Ex2ndPasswordCheck(1) });
		}
		else
		{
			_activeClient.sendPacket(new L2GameServerPacket[] { new Ex2ndPasswordCheck(0) });
		}
	}

	public boolean isAuthed()
	{
		return _authed;
	}

	private String cryptPassword(String password)
	{
		try
		{
			MessageDigest md = MessageDigest.getInstance("SHA");
			byte[] raw = password.getBytes("UTF-8");
			byte[] hash = md.digest(raw);
			return Base64.encodeBytes(hash);
		}
		catch (NoSuchAlgorithmException e)
		{
			_log.severe("[SecondaryPasswordController]Unsupported Algorythm");
		}
		catch (UnsupportedEncodingException e)
		{
			_log.severe("[SecondaryPasswordController]Unsupported Encoding");
		}
		return null;
	}
	
	private boolean validatePassword(String password)
	{
		if (!Util.isDigit(password))
		{
			return false;
		}
		if (password.length() < 6 || password.length() > 8)
		{
			return false;
		}
		if (Config.SAStrongPass)
		{
			for (int i = 0; i < password.length()-1; i++)
			{
				char curCh = password.charAt(i);
				char nxtCh = password.charAt(i+1);
				if (curCh+1 == nxtCh)
				{
					return false;
				}
				else if (curCh-1 == nxtCh)
				{
					return false;
				}
				else if (curCh == nxtCh)
				{
					return false;
				}
			}
	             
			for (int i = 0; i < password.length()-2; i++)
			{
				String toChk = password.substring(i+1);
				StringBuffer chkEr = new StringBuffer(password.substring(i, i+2));
				if (toChk.contains(chkEr))
				{
					return false;
				}
				else if (toChk.contains(chkEr.reverse()))
				{
					return false;
				}
			}	
		}

		
		_wrongAttempts = 0;
		return true;
	}
}